using System;
using System.IO;
using Environment = NFreeMarker.Core.Environment;

namespace NFreeMarker.Template
{
    /// <summary>
    /// The FreeMarker classes usually use this exception and its descendants to
    /// signal FreeMarker specific exceptions.
    /// </summary>
    public class TemplateException : Exception
    {
        /** The underlying cause of this exception, if any */
        private readonly Exception _causeException;
        private readonly Environment _env;
        private readonly string _ftlInstructionStack;

        /// <summary>
        /// Constructs a TemplateException with no specified detail message
        /// or underlying cause.
        /// </summary>
        /// 
        /// <param name="env">The current environment.</param>
        public TemplateException(Environment env)
            : this(null, null, env)
        {
        }

        /// <summary>
        /// Constructs a TemplateException with the given detail message,
        /// but no underlying cause exception.
        /// </summary>
        /// 
        /// <param name="description">The description of the error that occurred.</param>
        /// <param name="env">The current environment.</param>
        public TemplateException(string description, Environment env)
            : this(description, null, env)
        {
        }

        /// <summary>
        /// Constructs a TemplateException with the given underlying Exception,
        /// but no detail message.
        /// </summary>
        /// 
        /// <param name="cause">The underlying <code>Exception</code> that caused this
        /// exception to be raised.</param>
        /// <param name="env">The current environment.</param>
        public TemplateException(Exception cause, Environment env)
            : this(null, cause, env)
        {
        }

        /// <summary>
        /// Constructs a TemplateException with both a description of the error
        /// that occurred and the underlying Exception that caused this exception
        /// to be raised.
        /// </summary>
        /// 
        /// <param name="description">The description of the error that occurred.</param>
        /// <param name="cause">The underlying <code>Exception</code> that caused this
        /// exception to be raised.</param>
        /// <param name="env">The current environment.</param>
        public TemplateException(string description, Exception cause, Environment env)
            : base(GetDescription(description, cause))
        {
            _causeException = cause;
            _env = env;
            if (env != null)
            {
                var sw = new StringWriter();
                env.OutputInstructionStack(sw);
                sw.Flush();
                _ftlInstructionStack = sw.ToString();
            }
            else
            {
                _ftlInstructionStack = "";
            }
        }

        private static string GetDescription(string description, Exception cause)
        {
            if (description != null)
            {
                return description;
            }
            if (cause != null)
            {
                return cause.GetType().FullName + ": " + cause.Message;
            }
            return "No error message";
        }

        /**
         * <p>Returns the underlying exception that caused this exception to be
         * generated.</p>
         * <p><b>Note:</b><br />
         * avoided calling it <code>getCause</code> to avoid name clash with
         * JDK 1.4 method. This would be problematic because the JDK 1.4 method
         * returns a <code>Throwable</code> rather than an <code>Exception</code>.</p>
         *
         * @return the underlying <code>Exception</code>, if any, that caused this
         * exception to be raised
         */
        public Exception GetCauseException()
        {
            return _causeException;
        }

        /**
         * Returns the quote of the problematic FTL instruction and the FTL stack strace.
         */
        public string GetFtlInstructionStack()
        {
            return _ftlInstructionStack;
        }

        /**
         * @return the execution environment in which the exception occurred
         */
        public Environment GetEnvironment()
        {
            return _env;
        }

        public virtual void PrintStackTrace(TextWriter pw)
        {
            pw.WriteLine();
            pw.WriteLine(Message);
            if (!string.IsNullOrEmpty(_ftlInstructionStack))
            {
                pw.WriteLine("The problematic instruction:");
                pw.WriteLine(_ftlInstructionStack);
            }
            pw.WriteLine(".NET backtrace for programmers:");
            pw.WriteLine("----------");
            pw.WriteLine(ToString());
        }
    }
}